######################################################################
#BasicCharacter.py
#
#Purpose:  A generic character model (for use with all humanoid characters)
#   plus a Marine character model subclass
#Author:  Cody Jackson
#Date:  6/16/06
#
#Copyright 2006 Cody Jackson
#This program is free software; you can redistribute it and/or modify it 
#under the terms of the GNU General Public License as published by the Free 
#Software Foundation; either version 2 of the License, or (at your option) 
#any later version.
#
#This program is distributed in the hope that it will be useful, but 
#WITHOUT ANY WARRANTY; without even the implied warranty of 
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 
#General Public License for more details.
#
#You should have received a copy of the GNU General Public License 
#along with this program; if not, write to the Free Software Foundation, 
#Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#----------------------------------------------------------------------
#Version 4.0
#   Revised character name and gender methods to allow for interactive 
#       or argument entries
#   Added *nix shebang path
#   Updated skills to show default MOS skills and add chosen value to default
#       value
#Version 3.0
#   Removed MOS subset catagories; added values to MOS dictionary
#   Added default MOS skills
#   Added Tank Commander and APC Commander MOS's
#   Combined separate Character and Marine class tests into one test method
#   Added character initiative methods
#Version 2.1
#   Corrected Marine rank methods
#Version 2.0
#   Added Marine subclass
#Version 1.0
#   Initial build
######################################################################

#!/usr/bin/env python
from dice_roller import multiDie

class Character:
    """Creation of a basic character type."""
    
    #---Class attributes
    default_attribs = {}
    
    def __init__(self):
        """Constructor to initialize each data member to zero."""
        
        #---General info
        self.name = ""
        self.gender = 0
        self.age = 0        
        
        #---Attribute info
        self.attrib = 0
        self.attrib_dict = self.default_attribs.copy()  #instance version
        self.setAttribute("str")
        self.setAttribute("intel")
        self.setAttribute("build")
        self.setAttribute("char")
        self.setAttribute("will")
        self.setAttribute("throw")
        self.setAttribute("mass")
        self.setAttribute("load")
        self.setAttribute("dex")
        self.coreInitiative = 0
        
        #---Hit point info
        self.hp_dict = {}
        self.body_part = 0
        self.setHitPoints("head")
        self.setHitPoints("torso")
        self.setHitPoints("rarm")
        self.setHitPoints("larm")
        self.setHitPoints("lleg")
        self.setHitPoints("rleg")
        
        #---Skills info
        self.chosenSkills = {}
        self.subskills = {}
        self.skillPoints = 0
        self.skillLevel = 0
        self.skillChoice = 0
        self.maximum = 0
        self.subskillChoices = []
        self.baseSkills = ["Armed Combat", "Unarmed Combat", "Throwing", "Small Arms", 
            "Heavy Weapons", "Vehicle Weapons", "Combat Engineer", "First Aid", 
            "Wheeled Vehicles", "Tracked Vehicles", "Aircraft", "Interrogation", 
            "Swimming", "Reconnaissance", "Mechanic", "Forward Observer", 
            "Indirect Fire", "Electronics", "Computers", "Gunsmith",
            "Supply", "Mountaineering", "Parachute", "Small Boats",
            "Harsh environments", "Xenomorphs", "Navigation", "Stealth",
            "Chemistry", "Biology"]
            
        #---Equipment list
        self.equipment = []
    
#---Get general info---#008000#FFFFFF------------------------------------------
    def setName(self, name = ""):
        """Get the name of the character."""
        
        ##If name isn't given as an argument, the interactive prompt will run
        if name != "":
            self.name = name
        else:
            self.name = raw_input("Please enter your character's name.\n")
    
    def setGender(self, gender = 0):
        """Get the gender of the character."""
        
        ##If gender isn't given as an argument, the interactive prompt will run
        if gender == "M" or gender == "m":
            self.gender = "Male"
        elif gender == "F" or gender == "f":
            self.gender = "Female"
        else:
            while 1:
                self.gender = raw_input("Select a gender:  1=Male, 2=Female: ")
                if int(self.gender) == 1:
                    self.gender = "Male"
                    break
                elif int(self.gender) == 2:
                    self.gender = "Female"
                    break
                else:
                    print "Invalid choice.  Please choose 1 or 2."
                    continue

    def setAge(self):
        """Calculate the age of character, between 18 and 45 years old."""
        
        self.age = 15 + multiDie(3,2) #3d10

#---Create attributes---#008000#FFFFFF------------------------------------------
    def setAttribute(self, attr):
        """Generate value for an attribute, between 2 and 20."""
        
        #---Get secondary attributes
        if attr == "throw":
           self.attrib_dict[attr] = self.attrib_dict["str"] * 2    #meters
        elif attr == "mass":
            self.attrib_dict[attr] = (self.attrib_dict["build"] * 5) + 15 #kg
        elif attr == "load":
            self.attrib_dict[attr] = (self.attrib_dict["str"] + 
                self.attrib_dict["build"]) #kg
        #---Get core attributes
        else:
            self.attrib_dict[attr] = multiDie(2, 2)   #2d10 
    
    def setHitPoints(self, body_part):    
        """Generate hit points for each body part, based on 'build' attribute."""
        
        if body_part == "head":
            self.hp_dict[body_part] = self.attrib_dict["build"]
        elif body_part == "torso":
            self.hp_dict[body_part] = self.attrib_dict["build"] * 4
        else:   #arms & legs
            self.hp_dict[body_part] = self.attrib_dict["build"] * 2
    
    def setInitiative(self):
        """Establishes the core initiative level of a character.
        
        This initiative level can be modified for different character types."""
        
        self.coreInitiative = multiDie(1,1)

#---Choose skills---#008000#FFFFFF----------------------------------------------
    def printSkills(self):
        """Print list of skills available for a character."""
        
        print "\nBelow is a list of available skills for your character."
        print "Some skills may have specializations and will be noted when chosen."
        for i in range(len(self.baseSkills)):
            print "     ", i, self.baseSkills[i],    #5 spaces between columns
            if i % 3 == 0:
                print "\n"

    def setSkills(self):
        """Calculate skill points and pick base skills and levels.
        
        Skill points are randomly determined by dice roll.
        Certain skills have specialty areas available and are chosen separately."""
        
        self.skillPoints = multiDie(2,2) * 10
        while self.skillPoints > 0:
            #---Choose skill
            self.printSkills()
            print "\n\nYou have", self.skillPoints, "skill level points available."
            self.skillChoice = int(raw_input("Please pick a skill: "))
            self.pick = self.baseSkills[self.skillChoice]   #Chosen skill
            print "\nYou chose", self.skillChoice, self.baseSkills[self.skillChoice], "."
            
            #---Determine maximum skill level
            if self.skillPoints > 98:
                self.maximum = 98
            else: self.maximum = self.skillPoints
            
            #---Choose skill level
            print "The maximum points you can use are", self.maximum, "."
            self.skillLevel = int(raw_input("What skill level would you like? "))
            if self.skillLevel > self.skillPoints:  #See if level chosen exceeds points available
                print "Sorry, you don't have that many points."
                continue
            ##The chosen skill may already be present due to MOS
            if self.chosenSkills.has_key(self.pick):    
                self.mosValue = self.chosenSkills.get(self.pick)
                self.chosenSkills[self.pick] = self.skillLevel + self.mosValue
            else:
                self.chosenSkills[self.pick] = self.skillLevel  #Chosen skill level
            self.skillPoints -= self.skillLevel #Decrement skill points
            
            #---Pick specialty
            if self.skillLevel >= 20:   #Minimum level for a specialty
                self.pickSubSkill(self.pick)
    
    def pickSubSkill(self, skill):
        """If a base skill level is 20 or greater, a specialty may be available,
        depending on the skill.
        
        Chosen skill text string passed in as argument."""
        
        self.skill = skill
        
        #---Set speciality lists
        if self.skill == "Armed Combat":
            self.subskillChoices = ["Blunt Weapons", "Edged Weapons", "Chain Weapons"]
        elif self.skill == "Unarmed Combat":
            self.subskillChoices = ["Grappling", "Pummeling", "Throttling"]
        elif self.skill == "Throwing":
            self.subskillChoices = ["Aerodynamic", "Non-aerodynamic"]
        elif self.skill == "Small Arms":
            self.subskillChoices = ["Flamer", "Pulse Rifle", "Smartgun", "Sniper rifle", "Pistol"]
        elif self.skill == "Heavy Weapons":
            self.subskillChoices = ["PIG", "RPG", "SADAR", "HIMAT", "Remote Sentry"]
        elif self.skill == "Vehicle Weapons":
            self.subskillChoices = ["Aircraft", "Land Vehicles", "Water Vehicles"]
        elif self.skill == "Combat Engineer":
            self.subskillChoices = ["Underwater demolitions", "EOD", "Demolitions", "Land structures",
                "Vehicle use", "Bridges"]
        elif self.skill == "Aircraft":
            self.subskillChoices = ["Dropship", "Conventional", "Helicopter"]
        elif self.skill == "Swimming":
            self.subskillChoices = ["SCUBA", "Snorkel"]
        elif self.skill == "Mechanic":
            self.subskillChoices = ["Wheeled", "Tracked", "Aircraft"]
        elif self.skill == "Electronics":
            self.subskillChoices = ["Radio", "ECM"]
        elif self.skill == "Computers":
            self.subskillChoices = ["Hacking", "Programming"]
        elif self.skill == "Gunsmith":
            self.subskillChoices = ["Small Arms", "Heavy Weapons", "Vehicles"]
        elif self.skill == "Parachute":
            self.subskillChoices = ["HALO", "HAHO"]
        elif self.skill == "Harsh Environments":
            self.subskillChoices = ["No atmosphere", "Non-terra"]
        else:
            return
        
        self.statement(self.skill)
        for i in range(len(self.subskillChoices)):
            print i, self.subskillChoices[i]
        self.choice = int(raw_input())
        if self.choice == -1:   #Specialization not desired
            return
        else:   #Speciality chosen
            print "You chose the", self.subskillChoices[self.choice], "specialty.\n\
It has an initial skill level of 10."
            self.subskills[self.subskillChoices[self.choice]] = 10
        print self.subskills
        return

    def statement(self, skill):
        """Prints a generic statement for choosing a skill specialty."""
        
        self.skill = skill
        print "\n", self.skill, "has specializations.  If you desire to specialize in\
 a field,\nplease choose from the following list.  Enter -1 if you don't want a\n\
specialization.\n"

#---Equipment access methods---#008000#FFFFFF-----------------------------------
    def setEquipment(self, item):
        """Add equipment to character's inventory."""
        
        self.equipment.append(item)
    
    def getEquipment(self):
        """Display equipment in inventory."""
        
        print self.equipment
 
class Marine(Character):
    """Specializiation of a basic character with MOS and default skills."""
    
    def __init__(self):
        """Initialize Marine specific values."""
        
        #---Class attributes
        self.marineInit = 0
        
        #---Rename attributes        
        Character.__init__(self)
        self.intel = self.attrib_dict["intel"]
        self.char = self.attrib_dict["char"]
        self.str = self.attrib_dict["str"]
        self.dex = self.attrib_dict["dex"]
        self.will = self.attrib_dict["will"]
        
        #---Rank attributes
        self.rank = ""
        self.rankName = ""
        self.modifier = self.intel * .1 #rank modifier
        
        #---MOS attributes
        self.mos = ""
        self.mosSpecialty = ""
        self.mosSelection = {0:"Supply", 1:"Crew Chief", 2:"Infantry"}
 
#---Determine rank---#008000#FFFFFF---------------------------------------------
    def setRank(self):
        """Determine rank of Marine."""        
        
        if (self.intel + self.char) > 23:   #senior ranks
            self.roll = multiDie(1,2) + self.modifierValue()
            self.rank = self.seniorRank(self.roll)
        else:   #junior ranks
            self.roll = multiDie(1,2)
            self.rank = self.lowerRank(self.roll)

        #---Convert numerical rank to full name
        if self.rank == "E1":
            self.rankName = "Private"
        elif self.rank == "E2":
            self.rankName = "Private First Class"
        elif self.rank == "E3":
            self.rankName = "Lance Corporal"
        elif self.rank == "E4":
            self.rankName = "Corporal"
        elif self.rank == "E5":
            self.rankName = "Sergeant"
        elif self.rank == "E6":
            self.rankName = "Staff Sergeant"
        elif self.rank == "E7":
            self.rankName = "Gunnery Sergeant"
        elif self.rank == "E8":
            self.rankName = "Master Sergeant"
        elif self.rank == "O1":
            self.rankName = "2nd Lieutenant"
        elif self.rank == "O2":
            self.rankName = "1st Lieutenant"
        elif self.rank == "O3":
            self.rankName = "Captain"
        elif self.rank == "O4":
            self.rankName = "Major"
        else: print "Invalid rank."

    def modifierValue(self):
        """Determine rank modifier value."""
        
        if self.modifier % 1 < .5:  #round down
            self.modifier = int(self.modifier)
        elif self.modifier %1 >= .5:    #round up
            self.modifier = int(self.modifier) + 1
        return self.modifier
        
    def lowerRank(self, roll):
        """Determine rank of junior marinenel."""
        
        if self.roll == 1:
            self.rank = "E1"
        elif self.roll == 2:
            self.rank = "E2"
        elif self.roll == 3 or self.roll == 4:
            self.rank = "E3"
        elif self.roll == 5 or self.roll == 6:
            self.rank = "E4"
        elif 7 <= self.roll <= 9:
            self.rank = "E5"
        elif self.roll == 10:
            self.rank = "E6"
        else:   #Shouldn't reach here
            print "Ranking roll invalid"
        return self.rank
    
    def seniorRank(self, roll):
        """Determine rank and if character is senior enlisted or officer."""
        
        if self.roll > 5:   #Character is officer
            self.new_roll = multiDie(1,2) + self.modifierValue()
            if 1 <= self.new_roll <= 3:
                self.rank = "O1"
            elif 4 <= self.new_roll <= 7:
                self.rank = "O2"
            elif 8 <= self.new_roll <= 10:
                self.rank = "O3"
            elif self.new_roll > 10:
                self.rank = "O4"
            else:   #Shouldn't reach here
                print "Ranking roll invalid"
        else:   #Character is senior enlisted
            self.new_roll = multiDie(1,2)
            if 1 <= self.new_roll <= 4:
                self.rank = "E6"
            elif 5 <= self.new_roll <= 8:
                self.rank = "E7"
            elif 9 <= self.new_roll <= 11:
                self.rank = "E8"
            elif self.new_roll > 11:
                self.rank = "E9"
            else: #Shouldn't reach here
                print "Ranking roll invalid"
        return self.rank
    
#---Get MOS---#008000#FFFFFF----------------------------------------------------
    def requirements(self):
        """Adds eligible MOS's to selection dictionary.
        
        Takes various character attributes and determines if additional MOS choices
        are available."""
        
        if self.intel >= 11:
            self.mosSelection[3] = "Medical"
        ##Value "4" is missing due to changing positions
        if self.str >= 12:
            self.mosSelection[5] = "Heavy Weapons"
        if (self.dex + self.intel + self.will) >= 40:
            if multiDie(1,3) >= 85:
                self.mosSelection[6] = "Scout/Sniper"
        if (self.str + self.dex + self.intel + self.will) >= 50:
            if multiDie(1,3) >= 85:
                self.mosSelection[7] = "Recon"
        if self.str >= 10 and self.intel >= 10:
            self.mosSelection[8] = "Combat Engineer"
        if self.dex >= 11:  #Armor MOS's
            self.mosSelection[9] = "Tank Driver"
            self.mosSelection[10] = "Tank Gunner"
            self.mosSelection[11] = "APC Driver"
            self.mosSelection[12] = "APC Gunner"
        if self.intel + self.will >= 27:    #Intelligence MOS's
            self.mosSelection[15] = "Intelligence Analyst"
            self.mosSelection[16] = "Interrogator"
            self.mosSelection[17] = "Intelligence: Xenomorphs"
        if self.intel >= 10 and self.dex >= 10 and ("O1" <= self.rank <= "O4"):
            self.mosSelection[18] = "Pilot"
            self.mosSelection[19] = "Co-Pilot/Gunner"
            ##Tank and APC Commander MOS have the same requirements as pilots
            ##  but are added after the 'Armor' MOS's.
            self.mosSelection[13] = "Tank Commander"
            self.mosSelection[14] = "APC Commander"

    def eligibleMOS(self):
        """Displays MOS's available to character."""
        
        print "You are eligible for the following MOS's:"
        for key in self.mosSelection.keys():
            print key, self.mosSelection[key]
        self.mosChoice = int(raw_input("\nPlease choose the number of the MOS you want: "))
        self.mos = self.mosSelection[self.mosChoice]    #Rename selection
        print "You chose the", self.mos, "specialty."
##Subclasses removed for possible later use
##      if self.mosChoice == 2 or self.mosChoice == 4 or self.mosChoice == 9 or self.mosChoice == 10:
##          self.mosSpecialty = self.subClass(self.mosChoice)
##      if self.mosSpecialty == "":
##          self.mos = self.mosSelection[self.mosChoice]
##      else:
##          self.mos = self.mosSelection[self.mosChoice] + " (" + self.mosSpecialty + ")"
        return self.mos
    
    def mosSkills(self, mosChoice):
        """Determine default skill levels based on MOS."""
        
        if self.mosChoice == 0: #Supply
            self.chosenSkills["Supply"] = 20
        elif self.mosChoice == 1:   #Crew Chief
            self.chosenSkills["Mechanic"] = 20
        elif self.mosChoice == 2:   #Infantry
            self.chosenSkills["Small Arms"] = 20
            self.chosenSkills["Armed Combat"] = 10
            self.chosenSkills["Unarmed Combat"] = 10
            self.chosenSkills["Swimming"] = 10
        elif self.mosChoice == 3:   #Medical
            self.chosenSkills["First Aid"] = 20
        elif self.mosChoice == 4:   #Not a value
            pass
        elif self.mosChoice == 5:   #Heavy Weapons
            self.chosenSkills["Heavy Weapons"] = 20
            self.chosenSkills["Small Arms"] = 10
            self.chosenSkills["Armed Combat"] = 10
            self.chosenSkills["Unarmed Combat"] = 10
        elif self.mosChoice == 6:   #Scout/Sniper
            self.chosenSkills["Recon"] = 15
            self.chosenSkills["Forward Observer"] = 10
            self.chosenSkills["Small Arms"] = 25
            self.chosenSkills["Armed Combat"] = 10
            self.chosenSkills["Unarmed Combat"] = 10
            self.chosenSkills["Swimming"] = 10
            self.chosenSkills["Navigation"] = 10
        elif self.mosChoice == 7:   #Recon
            self.chosenSkills["Recon"] = 20
            self.chosenSkills["Forward Observer"] = 10
            self.chosenSkills["Small Arms"] = 20
            self.chosenSkills["Armed Combat"] = 10
            self.chosenSkills["Unarmed Combat"] = 10
            self.chosenSkills["Swimming"] = 10
            self.chosenSkills["Mountaineering"] = 10
            self.chosenSkills["Parachute"] = 10
            self.chosenSkills["Navigation"] = 10
        elif self.mosChoice == 8:   #Combat Engineer
            self.chosenSkills["Combat Engineer"] = 20
        elif self.mosChoice == 9:   #Tank Driver
            self.chosenSkills["Tracked Vehicles"] = 20
        elif self.mosChoice == 11:  #APC Driver
            self.chosenSkills["Wheeled Vehicles"] = 20
        elif self.mosChoice == 10 or self.mosChoice == 12:  #Vehicle Gunners
            self.chosenSkills["Vehicle Weapons"] = 20
            self.chosenSkills["Indirect Fire"] = 10
        elif self.mosChoice == 13 or self.mosChoice == 14:  #Vehicle Commanders
            self.chosenSkills["Navigation"] = 15
            self.chosenSkills["Tracked Vehicles"] = 10
            self.chosenSkills["Wheeled Vehicles"] = 10
            self.chosenSkills["Vehicle Weapons"] = 10
            self.chosenSkills["Indirect Fire"] = 10
            self.chosenSkills["Forward Observer"] = 10
        elif self.mosChoice == 15:  #Analyst
            self.chosenSkills["Electronics"] = 20
            self.chosenSkills["Computers"] = 20
        elif self.mosChoice == 16:  #Interrogation
            self.chosenSkills["Electronics"] = 10
            self.chosenSkills["Computers"] = 10
            self.chosenSkills["Interrogation"] = 20
        elif self.mosChoice == 17:  #Xenomorphs
            self.chosenSkills["Electronics"] = 10
            self.chosenSkills["Computers"] = 10
            self.chosenSkills["Xenomorphs"] = 20
            self.chosenSkills["Biology"] = 10
            self.chosenSkills["Chemistry"] = 10
        elif self.mosChoice == 18:  #Pilot
            self.chosenSkills["Aircraft"] = 20
            self.chosenSkills["Vehicl Weapons"] = 10
        elif self.mosChoice == 19:  #Co-Pilot/Gunner
            self.chosenSkills["Aircraft"] = 10
            self.chosenSkills["Vehicle Weapons"] = 20
        else: print "**Invalid option**"    #Shouldn't reach
        print "\nYou have the following default skills:", self.chosenSkills
    
##Subclasses removed for possible later use
##    #---Determine subclass of MOS
##    #def subClass(self, mosChoice):
##        """Notifies and allows user selection of an MOS specialty."""
##        self.subMOS = {1:"Small Arms", 2:"Smartgunner", 3:"Incinerator",
##        4:"Tank Driver", 5:"Tank Gunner", 6:"Tank Commander", 7:"APC Driver",
##        8:"APC Gunner", 9:"APC Commander", 10:"Analyst", 11:"Interrogation",
##        12:"Xenomorphs", 13:"Pilot", 14:"Copilot/Gunner"}
##        self.statement = "\nYou have chosen an MOS that has a subcategory.  Please\
##        choose\nyour specialty from the list below."
##        print self.mosChoice, self.mosSelection[self.mosChoice]
##        if self.mosChoice == 2: #Infantry: Light Weapons
##            print self.statement
##            for key in range(1, 4):
##                print key, self.subMOS[key]
##        elif self.mosChoice == 4:    #Pilot/Copilot
##            print self.statement
##            for key in range(13, 15):
##                print key, self.subMOS[key]
##        elif self.mosChoice == 9:   #Armor
##            print self.statement
##            for key in range(4, 10):
##                print key, self.subMOS[key]
##        elif self.mosChoice == 10:  #Intel
##            print self.statement
##            for key in range(10, 13):
##                print key, self.subMOS[key]
##        self.subMOSChoice = int(raw_input())
##        print "You chose the", self.subMOS.get(self.subMOSChoice), "specialty."
##        return self.subMOS.get(self.subMOSChoice)    
    
#---Base initiative---#008000#FFFFFF--------------------------------------------
    def marineInitiative(self):
        """Creates the base level for a Marine character type.
        
        Marines with an MOS of Sniper/Scout, Recon, or Pilot gain +1 to initiative.
        Marines with an MOS of Suppy, Intelligence, or Medical lose -1 to initiative."""
        
        if self.mosChoice == 6 or self.mosChoice == 7 or self.mosChoice == 18:
            self.marineInit = self.coreInitiative + 1
        elif self.mosChoice == 0 or self.mosChoice == 3 or 15 <= self.mosChoice <= 17:
            self.marineInit = self.coreInitiative -1
        else: self.marineInit = self.coreInitiative

def test1():
    """Test criteria for entire module."""
    
    marine = Marine()
    #marine.setName()
    #marine.setGender()
    marine.setAge()
    marine.setEquipment("Rucksack")
    marine.setEquipment("Knife")
    marine.setEquipment("Flashlight")
    marine.setRank()
    marine.requirements()
    marine.eligibleMOS()
    marine.mosSkills(marine.mosChoice)
    marine.setSkills()
    marine.setInitiative()
    marine.marineInitiative()
    
    print "\nName = ", marine.name
    print "Gender = ", marine.gender
    print "Age = ", marine.age
    print "Rank: ", marine.rank
    print "Rank name: ", marine.rankName
    for key in marine.attrib_dict.keys():
        print key, marine.attrib_dict[key]
    for key in marine.hp_dict.keys():
        print key, marine.hp_dict[key]
    print "Your MOS is: ", marine.mos
    print marine.chosenSkills
    print marine.subskills    
    marine.getEquipment()
    print marine.marineInit
    
def test2():
    """Test criteria for changes to name and gender methods."""
    
    marine = Marine()
    marine.setName("Gerry Mander")
    marine.setGender("F")
    print "Name = ", marine.name
    print "Gender = ", marine.gender
    
    
if __name__ == "__main__":
    #test1()
    test2()